/*
 * Title:        CloudScheduler Toolkit
 * Description:  a Toolkit for Modeling and Simulation of Job Scheduling and Resource Provisioning in Cloud System
 * Licence:      GPL - http://www.gnu.org/copyleft/gpl.html
 * Author:       Fuhui Wu
 *
 * Copyright (c) 2013-2014, The National University of Defense Technology, China
 */

package org.nudt.jCloud.simulation;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;

import org.nudt.jCloud.resourceManager.ResManager;
import org.nudt.jCloud.util.Log;

public class CloudSchedSIM {
	private static final String CLUSTERSIM_VERSION_STRING = "0.1";
	private static CloudInfoService CIS;	
	private static List<SimEntity> entities;
	private static double clock;	
	private static FutureQueue future;
	private static DeferredQueue deferred;	
	private static Map<Integer, Predicate> waitPredicates;
	private static boolean running;	
	public static Predicate SIM_ANY = new PredicateAny();
	public static String name;
	
	public static void init(String simName){
		name = simName;
		entities = new ArrayList<SimEntity>();
		future = new FutureQueue();
		deferred = new DeferredQueue();
		waitPredicates = new HashMap<Integer, Predicate>();
		CIS = new CloudInfoService("ClusterInfoService");
		addEntity(CIS);
		clock = -2;
		setRunning(false);
	}
	
	public static void addRM(ResManager rM){
		addEntity(rM);
		rM.send(CloudSchedSIM.getCIS().getId(), 1, SIMTag.REGISTER_RESOURCE, rM.getId());
	}
		
	public static void addUserBroker(UserBroker userBroker){
		addEntity(userBroker);
		userBroker.send(CloudSchedSIM.getCIS().getId(), 2, SIMTag.REGISTER_USERBROKER, userBroker.getId());
	}
	
	public static void reset(String simName){
		name = simName;
		clock = -2;
		CIS.reset();
		entities.clear();
		CIS = new CloudInfoService("ClusterInfoService");
		addEntity(CIS);
		setRunning(false);
	}
	
	public static double startSimulation(){
		Log.println("Starting simulation...");
		Log.init();
		double clock = run();
		return clock;
	}
	
	private static double run() {
		runStart();
		
		while(true){
			if(runClockTick()){
				break;
			}
		}
		double clock = getClock();
		return clock;
	}

	private static boolean runClockTick() {
		SimEntity ent;
		boolean queueIsEmpty;
		int entityCount = entities.size();
		for(int i=0; i<entityCount; ++i){
			ent = entities.get(i);
			ent.run();
		}
		if(future.size() > 0){
			List<SimEvent> toRemove = new ArrayList<SimEvent>();
			Iterator<SimEvent> fit = future.iterator();
			queueIsEmpty = false;
			SimEvent first = fit.next();
			processEvent(first);
			future.remove(first);
			
			fit = future.iterator();
			
			boolean trymore = fit.hasNext();
			while(trymore){
				SimEvent next = fit.next();
				if(next.getTime() == first.getTime()){
					processEvent(next);
					toRemove.add(next);
					trymore = fit.hasNext();
				}else{
					trymore = false;
				}
			}
			
			future.removeAll(toRemove);
		}else{
			queueIsEmpty = true;
			running = false;
			Log.println("Simulation: No more future events");
		}
		return queueIsEmpty;
	}

	private static void processEvent(SimEvent e) {
		int dest, src;
		SimEntity dest_ent;
		if(e.getTime() < clock){
			throw new IllegalArgumentException("Past event detected.");
		}
		clock = e.getTime();
		
		switch(e.getEventType()){
			case SimEvent.SEND:
				dest = e.getDst();
				if(dest < 0){
					throw new IllegalArgumentException("Attempt to send to a null entity detected.");
				}else{
					int tag = e.getTag();
					dest_ent = entities.get(dest);
					if(dest_ent.getState() == SimEntity.WAITING){
						Integer destObj = Integer.valueOf(dest);
						Predicate p = waitPredicates.get(dest);
						if((p==null) || (tag == 9999) || (p.match(e))){
							dest_ent.setEventBuf((SimEvent)e.clone());
							dest_ent.setState(SimEntity.RUNNABLE);
							waitPredicates.remove(destObj);
						}else{
							deferred.addEvent(e);
						}
					}else{
						deferred.addEvent(e);
					}
				}
				break;
			case SimEvent.HOLD_DONE:
				src = e.getSrc();
				if(src<0){
					throw new IllegalArgumentException("Null entity holding.");
				}else{
					entities.get(src).setState(SimEntity.RUNNABLE);
				}
				break;
			default:
				break;
		}
		
	}

	private static void runStart() {
		setRunning(true);
		for(SimEntity ent : entities){
			ent.startEntity();
		}
		Log.println("All entities started...");
	}
	
	public static void send(int src, int dest, double delay, int tag, Object data){
		if(delay<0){
			throw new IllegalArgumentException("Send delay can't be negative.");
		}
		
		SimEvent e = new SimEvent(SimEvent.SEND, clock + delay, src, dest, tag, data);
		future.addEvent(e);
	}

	/**
	 * when new entity is constructed, this method is called to add the 
	 * new entity to entities array, and the new entity's id is set.
	 * @param entity
	 */
	public static void addEntity(SimEntity entity){
		int id = entities.size();
		entity.setId(id);
		entities.add(entity);
	}

	/**
	 * @return the clustersimVersionString
	 */
	public static String getClustersimVersionString() {
		return CLUSTERSIM_VERSION_STRING;
	}


	/**
	 * @return the entities
	 */
	public static List<SimEntity> getEntities() {
		return entities;
	}


	/**
	 * @param entities the entities to set
	 */
	public static void setEntities(List<SimEntity> entities) {
		CloudSchedSIM.entities = entities;
	}


	/**
	 * @return the clock
	 */
	public static double getClock() {
		return clock;
	}


	/**
	 * @param clock the clock to set
	 */
	public static void setClock(double clock) {
		CloudSchedSIM.clock = clock;
	}

	/**
	 * @return the cIS
	 */
	public static CloudInfoService getCIS() {
		return CIS;
	}

	/**
	 * @param cIS the cIS to set
	 */
	public static void setCIS(CloudInfoService cIS) {
		CIS = cIS;
	}

	/**
	 * @return the future
	 */
	public static FutureQueue getFuture() {
		return future;
	}

	/**
	 * @param future the future to set
	 */
	public static void setFuture(FutureQueue future) {
		CloudSchedSIM.future = future;
	}

	/**
	 * @return the deferred
	 */
	public static DeferredQueue getDeferred() {
		return deferred;
	}

	/**
	 * @param deferred the deferred to set
	 */
	public static void setDeferred(DeferredQueue deferred) {
		CloudSchedSIM.deferred = deferred;
	}

	/**
	 * @return the running
	 */
	public static boolean isRunning() {
		return running;
	}

	/**
	 * @param running the running to set
	 */
	public static void setRunning(boolean running) {
		CloudSchedSIM.running = running;
	}

	public static int waiting(int dst, Predicate p) {
		int count = 0;
		SimEvent event;
		Iterator<SimEvent> iterator = deferred.iterator();
		while(iterator.hasNext()){
			event = iterator.next();
			if((event.getDst()==dst) && (p.match(event))){
				count++;
			}
		}
		return count;
	}

	public static SimEvent select(int dst, Predicate p) {
		SimEvent event = null;
		Iterator<SimEvent> iterator = deferred.iterator();
		while(iterator.hasNext()){
			event = iterator.next();
			if(event.getDst()==dst && p.match(event)){
				iterator.remove();
				break;
			}
		}
		return event;
	}
	
	public static String getEntityName(int src) {
		return entities.get(src).getName();
	}
	
	public static int getEntityIdByName(String name){
		for(int i=0; i<entities.size(); ++i){
			if(entities.get(i).getName() == name){
				return entities.get(i).getId();
			}
		}
		return -1;
	}
	
	public static SimEntity getEntityById(int id){
		for(SimEntity entity : entities){
			if(entity.getId()==id){
				return entity;
			}
		}
		return null;
	}

}
